This notebook runs the GP backtest, NN-IV training and backtesting, SSVI backtesting and trinomial tree backtesting. Finally all results are compared.
If you run this notebook on google colab you need to upload python scripts on the left panel. To that end click on the left "Files" (or "Fichiers" in French) and drag and drop :
import sys
formerPath = sys.path
sys.path.append('./code/')
sys.path.append('./BS/')
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import sklearn as skl
import bootstrapping
import dataSetConstruction
import backtest
import BS
import loadData
import plotTools
import SSVI
import importlib
import neuralNetwork
from Bisect import bisect
from newton import newton
from BSImplVol import bsimpvol
%matplotlib inline
In order to reproduce our paper experiments, execute cells from part "Load preformatted data".
Each source of data produces the following objects :
importlib.reload(loadData)
pd.options.mode.chained_assignment = 'raise'
workingFolder = "./data/"
fileName = "Option_SPX_18_Mai_2019Feuille2.xlsm"
asOfDate = "2019-05-18"
trainingSet, testingSet, bootstrap, S0 = loadData.loadCBOTData(workingFolder, fileName, asOfDate)
importlib.reload(bootstrapping)
workingFolder = "./data/"
trainingSet, testingSet, bootstrap, S0 = loadData.loadFormattedData(workingFolder)
We assume a piecewise constant discount short rate $r$ and a piecewise constant dividend short rate $q$.
We estimate the "zero coupon dividend" $D(T) = e^{-\int_{0}^{T} q_s ds}$ by regressing it against maturity : $$e^{-\int_{0}^{T} q_s ds} = \frac{C(T,K) - P(T,K) + K e^{-\int_{0}^{T} r_s ds}}{S_0}$$
Then we have $\hat{q}_t = - \frac{ \log{D(\overline{T})} - \log{D(\underline{T})} }{ \overline{T} - \underline{T} }$ with $\overline{T}$ the smallest discretized maturity greater than $T$ and $\underline{T}$ the grestest discretized maturity inferior than $T$.
bootstrap object has several members :
These curve should satisfy the call-put parity.
Neural network on modified prices with modified strike as input such that discounting and dividend don't intervene in Dupire formula calculation.
dataSet = trainingSet #Training set
dataSetTest = testingSet #Testing set
dataSet.head()
dataSetTest.head()
dfCurve = dataSetConstruction.savingData(bootstrap,
dataSet,
dataSetTest,
workingFolder)
threshold = pd.concat([dataSet, dataSetTest])["Price"].sort_values().iloc[int(0.05 * pd.concat([dataSet, dataSetTest]).shape[0])]
threshold
dataSet.shape
dataSetTest.shape
S0
def getCall(df):
return df[df["OptionType"]==1]
def getPut(df):
return df[df["OptionType"]==-1]
plotTools.plotSerie(getPut(trainingSet)["ImpliedVol"],
yMin=0,
yMax=3*S0,
Title = 'True Price Surface')
plotTools.plotSerie(getPut(testingSet)["ImpliedVol"],
yMin=0,
yMax=3*S0,
Title = 'True Price Surface')
plotTools.plot2Series(getPut(testingSet)["Price"],
getPut(trainingSet)["Price"],
yMin=0,
yMax=3*S0,
Title = 'True Price Surface')
Use min-max of scaling strike between 0 et 1 for improving stability of neural network training.
activateScaling = False
transformCustom = dataSetConstruction.transformCustomMinMax if activateScaling else dataSetConstruction.transformCustomId
inverseTransform = dataSetConstruction.inverseTransformMinMax if activateScaling else dataSetConstruction.inverseTransformId
inverseTransformColumn = dataSetConstruction.inverseTransformColumnMinMax if activateScaling else dataSetConstruction.inverseTransformColumnId
inverseTransformColumnGreeks = dataSetConstruction.inverseTransformColumnGreeksMinMax if activateScaling else dataSetConstruction.inverseTransformColumnGreeksId
scaler = skl.preprocessing.MinMaxScaler(feature_range=(0, 1))
scaler.fit(dataSet)
scaledDataSet = transformCustom(dataSet, scaler)
scaledDataSetTest = transformCustom(dataSetTest, scaler)
scaledDataSet.head()
#Search strike for ATM option
midS0 = dataSet[dataSet.index.get_level_values("Strike") >= S0].index.get_level_values("Strike").min()
KMin = 0
KMax = 2.0 * S0
The purpose of this section is to load the GP local volatility surface and perform the Monte-Carlo backtest of the option prices uses the GP local volatility surface. Note that the GP local volatility is generated by running the Matlab code in the "code/GP" folder. See Section "GP Local Volatility Backtests" below for further details of the backtests.
This section loads result from the Matlab experiments. See code/GP folder to access the matlab script.
pathFolder = "./data/"
fileName = "GP_output_Put_Price_testing_set.xlsx"
putTestingGP = pd.read_excel(pathFolder + fileName,
header=0,
sheet_name = "Sheet1")
putTestingGP["Strike"] = np.exp(+ bootstrap.discountIntegral(putTestingGP["T"])
- bootstrap.dividendIntegral(putTestingGP["T"])) * putTestingGP["K"]
putTestingGP["Maturity"] = putTestingGP["T"]
putTestingGP = putTestingGP.set_index(["Strike","Maturity"], drop=False)
putTestingGP = pd.DataFrame(putTestingGP.values, columns = putTestingGP.columns, index = dataSetTest.index)
putTestingGP["Strike"] = dataSetTest["Strike"]
putTestingGP["Maturity"] = dataSetTest["Maturity"]
putTestingGP.head()
putTestingGP.tail()
dataSet.head()
ImpVolPutTesting = BS.vectorizedImpliedVolatilityCalibration(S0, bootstrap,
putTestingGP["T"],
putTestingGP["Strike"],
-1 * np.ones_like(putTestingGP["Strike"]),
putTestingGP["GP_Put_price"],
removeNaN= False)
ImpVolPutTesting = pd.Series(ImpVolPutTesting, index = putTestingGP.set_index(["Strike","Maturity"], drop=False).index).sort_index()
plotTools.predictionDiagnosis(putTestingGP["GP_Put_price"],
dataSetTest['Price'],#ImpVolPutTestingRef, #
" Price ",
yMin=2400,
yMax=3600,
az = 225,
threshold=None)
keptPrices = ImpVolPutTesting.dropna().index
plotTools.predictionDiagnosis(plotTools.selectIndex(ImpVolPutTesting, keptPrices),
plotTools.selectIndex(dataSetTest["ImpliedVol"], keptPrices),
" Implied vol ",
yMin=2400,
yMax=3600,
az = 225)
pathFolder = "./data/"
fileName = "GP_output_Put_Price_training_set.xlsx"
putTrainingGP = pd.read_excel(pathFolder + fileName,
header=0,
sheet_name = "Sheet1")
putTrainingGP["Strike"] = np.exp(+ bootstrap.discountIntegral(putTrainingGP["T"])
- bootstrap.dividendIntegral(putTrainingGP["T"])) * putTrainingGP["K"]
putTrainingGP["Maturity"] = putTrainingGP["T"]
putTrainingGP = putTrainingGP.set_index(["Strike","Maturity"], drop=False)
putTrainingGP = pd.DataFrame(putTrainingGP.values, columns = putTrainingGP.columns, index = dataSet.index)
putTrainingGP["StrikeGap"] = dataSet["Strike"] - putTrainingGP["Strike"]
putTrainingGP["Strike"] = dataSet["Strike"]
putTrainingGP["Maturity"] = dataSet["Maturity"]
putTrainingGP["OriginalPrice"] = putTrainingGP["GP_Put_price"] * np.exp(- bootstrap.dividendIntegral(putTrainingGP["T"]))
putTrainingGP.head()
ImpVolPutTraining = BS.vectorizedImpliedVolatilityCalibration(S0, bootstrap,
putTrainingGP["T"],
putTrainingGP["Strike"],
-1 * np.ones_like(putTrainingGP["Strike"]),
putTrainingGP["GP_Put_price"],
removeNaN= False)
ImpVolPutTraining = pd.Series(ImpVolPutTraining, index = putTrainingGP.set_index(["Strike","Maturity"], drop=False).index).sort_index()
plotTools.predictionDiagnosis(putTrainingGP["GP_Put_price"],
dataSet['Price'],
" Implied vol ",
yMin=2400,
yMax=3600,
az = 30,
threshold=None)
keptPrices = ImpVolPutTraining.dropna().index
plotTools.predictionDiagnosis(plotTools.selectIndex(ImpVolPutTraining, keptPrices),
plotTools.selectIndex(dataSet["ImpliedVol"], keptPrices),
" Implied vol ",
yMin=2400,
yMax=3600,
az = 180)
strikeLow = min(dataSet["Strike"].min(),dataSetTest["Strike"].min()) #dataSet["Strike"].min()
strikeUp = max(dataSet["Strike"].max(),dataSetTest["Strike"].max()) #dataSet["Strike"].max()
strikeGrid = np.linspace(strikeLow, strikeUp, 100)
matLow = min(dataSet["Maturity"].min(),dataSetTest["Maturity"].min()) #dataSet["Maturity"].min()
matUp = max(dataSet["Maturity"].max(),dataSetTest["Maturity"].max()) #dataSetTest["Maturity"].max()
matGrid = np.linspace(matLow, matUp, 100)
volLocaleGrid = np.meshgrid(strikeGrid, matGrid)
volLocaleGridDf = pd.DataFrame(np.vstack((np.ravel(volLocaleGrid[0]), np.ravel(volLocaleGrid[1]))).T,
columns = ["Strike", "Maturity"]).set_index(["Strike","Maturity"],drop=False)
volLocaleGridDf["ChangedStrike"] = bootstrap.changeOfVariable(volLocaleGridDf["Strike"], volLocaleGridDf["Maturity"])[0]
volLocaleGridDf["logMoneyness"] = np.log(volLocaleGridDf["ChangedStrike"] / S0)
volLocaleGridDf["OptionType"] = np.ones_like(volLocaleGridDf["logMoneyness"])
workingFolder = "./data/volLocaleAresky/"
#filename = "local_vol_nx_10_nt_27.xlsx" RMSE : 10.073967351737087
#filename = "local_vol_nx_12_nt_27.xlsx" RMSE : 7.758802111118254
#filename = "local_vol_nx_15_nt_27.xlsx" RMSE : 6.252799135416868
#filename = "local_vol_nx_18_nt_27.xlsx" RMSE : 5.083806059940602
#filename = "local_vol_nx_20_nt_27.xlsx" RMSE : 5.050554384554841
#filename = "local_vol_nx_25_nt_27.xlsx" RMSE : 5.499835852015688
filename = "local_vol_nx_20_nt_27.xlsx"
locVolGP = loadData.loadGPLocVol(workingFolder, filename, bootstrap, S0)
locVolGP.head()
filterLocVol = locVolGP[(locVolGP["Maturity"] <= matUp) & (locVolGP["Maturity"] >= matLow)]
plotTools.plotSerie(filterLocVol["LocalVolatility"],
Title = 'GP local volatility',
az=30,
yMin=KMin,
yMax=KMax,
zAsPercent=True)
filterLocVol = locVolGP[(locVolGP["Maturity"] <= matUp) & (locVolGP["Maturity"] >= matLow)]
plotTools.plotSerie(plotTools.convertToLogMoneyness(filterLocVol["LocalVolatility"], S0),
Title = 'GP local volatility',
az=30,
yMin=KMin,
yMax=KMax,
zAsPercent=True)
nnGP = lambda x,y : backtest.interpolatedMCLocalVolatility(locVolGP["LocalVolatility"], x, y)
filterLocVol = locVolGP[(locVolGP["Maturity"] <= matUp) & (locVolGP["Maturity"] >= matLow)]
plotTools.plotSerie(nnGP(dataSetTest["Strike"], dataSetTest["Maturity"]),
Title = 'GP local volatility',
az=30,
yMin=KMin,
yMax=KMax,
zAsPercent=True)
filterLocVol = locVolGP[(locVolGP["Maturity"] <= matUp) & (locVolGP["Maturity"] >= matLow)]
plotTools.plotSerie(plotTools.convertToLogMoneyness(nnGP(dataSetTest["Strike"], dataSetTest["Maturity"]), S0),
Title = 'GP local volatility',
az=30,
yMin=KMin,
yMax=KMax,
zAsPercent=True)
logMin = np.log(KMin/S0),
logMax = 0.1 #np.log(KMax/S0),
plotTools.plot2Series(plotTools.convertToLogMoneyness(nnGP(dataSet["Strike"], dataSet["Maturity"]), S0),
plotTools.convertToLogMoneyness(nnGP(dataSetTest["Strike"], dataSetTest["Maturity"]), S0),
yMin=logMin,
yMax=logMax,
az = 340,
Title = 'True Implied Vol Surface')
filterLocVol = locVolGP[(locVolGP["Maturity"] <= matUp) & (locVolGP["Maturity"] >= matLow)]
plotTools.plotSerie(nnGP(volLocaleGridDf["Strike"], volLocaleGridDf["Maturity"]),
Title = 'GP local volatility',
az=30,
yMin=KMin,
yMax=KMax,
zAsPercent=True)
plotTools.saveDataModel(plotTools.removeDuplicateIndex(putTrainingGP["GP_Put_price"]),
plotTools.removeDuplicateIndex(nnGP(dataSet["Strike"], dataSet["Maturity"])),
plotTools.removeDuplicateIndex(ImpVolPutTraining) ,
"./Results/GPTraining")
plotTools.saveDataModel(plotTools.removeDuplicateIndex(putTestingGP["GP_Put_price"]),
plotTools.removeDuplicateIndex(nnGP(dataSetTest["Strike"], dataSetTest["Maturity"])),
plotTools.removeDuplicateIndex(ImpVolPutTesting) ,
"./Results/GPTesting")
During Monte Carlo backtest, each option in testing set is priced with an underlying which is diffused with the following SDE : $$ dS_t = \left( r_t - q_t - \frac{\sigma_{NN}^2(t, S_t)}{2} \right) dt + \sigma_{NN}(t, S_t) dW_t$$ with $\sigma_{NN}$ the neural local volatility function.
Due to computation time issue we avoid to make millions of call to neural network and we interpolate linearly neural local volatility obtained on one the two possible grid :
During PDE backtest, we used a crank-nicholson scheme to revaluate each option in our testing set. Time step corresponds to one day and space grid has 100 points.
nbTimeStep = 100
nbPaths = 100000
importlib.reload(backtest)
nnGP = lambda x,y : backtest.interpolatedMCLocalVolatility(locVolGP["LocalVolatility"], x, y)
strikeLow = min(dataSet["Strike"].min(),dataSetTest["Strike"].min()) #dataSet["Strike"].min()
strikeUp = max(dataSet["Strike"].max(),dataSetTest["Strike"].max()) #dataSet["Strike"].max()
strikeGrid = np.linspace(strikeLow, strikeUp, 100)
matLow = min(dataSet["Maturity"].min(),dataSetTest["Maturity"].min()) #dataSet["Maturity"].min()
matUp = max(dataSet["Maturity"].max(),dataSetTest["Maturity"].max()) #dataSetTest["Maturity"].max()
matGrid = np.linspace(matLow, matUp, 100)
volLocaleGrid = np.meshgrid(strikeGrid, matGrid)
volLocaleGridDf = pd.DataFrame(np.vstack((np.ravel(volLocaleGrid[0]), np.ravel(volLocaleGrid[1]))).T,
columns = ["Strike", "Maturity"]).set_index(["Strike","Maturity"],drop=False)
volLocaleGridDf["ChangedStrike"] = bootstrap.changeOfVariable(volLocaleGridDf["Strike"], volLocaleGridDf["Maturity"])[0]
volLocaleGridDf["logMoneyness"] = np.log(volLocaleGridDf["ChangedStrike"] / S0)
volLocaleGridDf["OptionType"] = np.ones_like(volLocaleGridDf["logMoneyness"])
volLocaleGridDf.head()
mcResGPTest = backtest.MonteCarloPricerVectorized(S0,
dataSetTest,
bootstrap,
nbPaths,
nbTimeStep,
nnGP)
mcResGPTest.head()
mcResGPTest.to_csv(workingFolder + "mcResGPTest.csv")
plotTools.predictionDiagnosis(mcResGPTest["Price"],
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
plotTools.plotSerie(mcResGPTest["stdPrice"],
Title = 'GP backtested price std',
az=30,
yMin=KMin,
yMax=KMax,
zAsPercent=True)
testingDataSetGP = dataSetConstruction.interpolatedLocalVolatility(locVolGP,
dataSetTest["Price"])
plotTools.plotSerie(testingDataSetGP,
Title = 'GP Local Volatility Surface',
az=30,
yMin=KMin,
yMax=KMax,
zAsPercent=True)
pdeResSigmaGPTest = backtest.PDEPricerVectorized(dataSetTest, S0, nnGP, bootstrap)
pdeResSigmaGPTest.head()
plotTools.predictionDiagnosis(pdeResSigmaGPTest,
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
pdeResSigmaGPTest.to_csv(workingFolder + "pdeResSigmaGPTest.csv")
importlib.reload(loadData)
Yon can skip the training step by loading in the left panel in colab workspace tensorflow models. These models are contained in the results folder of github repository.
hyperparameters = {}
#penalization coefficient
hyperparameters["lambdaLocVol"] = 0.11#1#0.001#0.01 #100
hyperparameters["lambdaSoft"] = 1#100#0.0001#10#10 #100
hyperparameters["lambdaGamma"] = 10#10000#100#10 #10000
#Derivative soft constraints parameters
hyperparameters["lowerBoundTheta"] = 0.01
hyperparameters["lowerBoundGamma"] = 0.00001
#Local variance parameters
hyperparameters["DupireVarCap"] = 10
hyperparameters["DupireVolLowerBound"] = 0.05
hyperparameters["DupireVolUpperBound"] = 0.40
#Learning scheduler coefficient
hyperparameters["LearningRateStart"] = 0.1
hyperparameters["Patience"] = 100
hyperparameters["batchSize"] = 50
hyperparameters["FinalLearningRate"] = 1e-6
hyperparameters["FixedLearningRate"] = False
#Training parameters
hyperparameters["nbUnits"] = 100 #number of units for hidden layers
hyperparameters["maxEpoch"] = 10000#10000 #maximum number of epochs
importlib.reload(plotTools)
#Execute this cell if you want to fit neural network with implied volatilities
res = neuralNetwork.create_train_model_gatheral(neuralNetwork.NNArchitectureVanillaSoftGatheral,
scaledDataSet,
True,
hyperparameters,
scaler,
modelName = "convexSoftGatheralVolModel")
y_pred4G, volLocale4G, dNN_T4G, gNN_K4G, lossSerie4G = res
plotTools.plotEpochLoss(lossSerie4G)
lossSerie4G.iloc[-1]
y_pred4G.head()
# Evaluate results on the training set, you can execute that cell without training the model
res = neuralNetwork.create_eval_model_gatheral(neuralNetwork.NNArchitectureVanillaSoftGatheral,
scaledDataSet,
True,
hyperparameters,
scaler,
modelName = "convexSoftGatheralVolModel")
y_pred4G, volLocale4G, dNN_T4G, gNN_K4G, lossSerie4G = res
plotTools.modelSummaryGatheral(y_pred4G,
volLocale4G,
dNN_T4G,
gNN_K4G,
dataSet,
yMin = KMin,
yMax = KMax,
S0 = S0,
bootstrap = bootstrap,
thresholdPrice = None,
savePath = "NeuralImpliedVolTrain")
volLocale4G.loc[(midS0,slice(None))]
# Evaluate results on the testing dataset, you can execute that cell without training the model
res = neuralNetwork.create_eval_model_gatheral(neuralNetwork.NNArchitectureVanillaSoftGatheral,
scaledDataSetTest,
True,
hyperparameters,
scaler,
modelName = "convexSoftGatheralVolModel")
y_pred4TestG, volLocale4TestG, dNN_T4TestG, gNN_K4TestG, lossSerie4TestG = res
plotTools.modelSummaryGatheral(y_pred4TestG,
volLocale4TestG,
dNN_T4TestG,
gNN_K4TestG,
dataSetTest,
yMin = KMin,
yMax = KMax,
S0 = S0,
bootstrap = bootstrap,
thresholdPrice = None,
savePath = "NeuralImpliedVolTest")
importlib.reload(plotTools)
#Diagnosis for training results
plotTools.modelSummaryGatheral(y_pred4G,
volLocale4G,
dNN_T4G,
gNN_K4G,
dataSet,
logMoneynessScale = True,
S0 = S0,
bootstrap = bootstrap,
thresholdPrice = None,
yMin = KMin + 0.0001,
yMax = KMax)
#Diagnosis for testing results
plotTools.modelSummaryGatheral(y_pred4TestG,
volLocale4TestG,
dNN_T4TestG,
gNN_K4TestG,
dataSetTest,
logMoneynessScale = True,
S0 = S0,
bootstrap = bootstrap,
thresholdPrice = None,
yMin = KMin + 0.0001,
yMax = KMax)
During Monte Carlo backtest, each option in testing set is priced with an underlying which is diffused with the following SDE : $$ dS_t = \left( r_t - q_t - \frac{\sigma_{NN}^2(t, S_t)}{2} \right) dt + \sigma_{NN}(t, S_t) dW_t$$ with $\sigma_{NN}$ the neural local volatility function.
Due to computation time issue we avoid to make millions of call to neural network and we interpolate linearly neural local volatility obtained on one the two possible grid :
During PDE backtest, we used a crank-nicholson scheme to revaluate each option in our testing set. Time step corresponds to one day and space grid has 100 points.
# Function which evaluates neural local volatility when neural network is fitted on implied volatilities
def neuralVolLocaleGatheral(s,t):
vLoc = neuralNetwork.evalVolLocaleGatheral(neuralNetwork.NNArchitectureVanillaSoftGatheral,
s, t,
dataSet,
hyperparameters,
scaler,
bootstrap,
S0,
modelName = "convexSoftGatheralVolModel")
return vLoc.dropna()
#Artificial grid with 10000 points which aims to limit the impact of linear interpolation
strikeLow = min(dataSet["Strike"].min(),dataSetTest["Strike"].min()) #dataSet["Strike"].min()
strikeUp = max(dataSet["Strike"].max(),dataSetTest["Strike"].max()) #dataSet["Strike"].max()
strikeGrid = np.linspace(strikeLow, strikeUp, 100)
matLow = min(dataSet["Maturity"].min(),dataSetTest["Maturity"].min()) #dataSet["Maturity"].min()
matUp = max(dataSet["Maturity"].max(),dataSetTest["Maturity"].max()) #dataSetTest["Maturity"].max()
matGrid = np.linspace(matLow, matUp, 100)
volLocaleGrid = np.meshgrid(strikeGrid, matGrid)
volLocalInterp7 = neuralVolLocaleGatheral(volLocaleGrid[0].flatten(),
volLocaleGrid[1].flatten())
volLocalInterp7.head()
volLocalInterp8 = neuralVolLocaleGatheral(dataSetTest.index.get_level_values("Strike").values.flatten(),
dataSetTest.index.get_level_values("Maturity").values.flatten())
volLocalInterp8.head()
#Local volatility function for backtests
nnVolLocale7 = lambda x,y : backtest.interpolatedMCLocalVolatility(volLocalInterp7, x, y)
#Local volatility function for backtests
nnVolLocale8 = lambda x,y : backtest.interpolatedMCLocalVolatility(volLocalInterp8, x, y)
importlib.reload(plotTools)
plotTools.plotSerie(volLocalInterp7,
Title = 'Local Volatility',
az=105,
yMin=KMin,
yMax=KMax,
zAsPercent=False)
plotTools.plotSerie(volLocalInterp8,
Title = 'Local Volatility',
az=30,
yMin=KMin,
yMax=KMax,
zAsPercent=True)
nbTimeStep = 100
nbPaths = 100000
#Local volatility is returned through linear interpolation on neural local volatilities obtained on the 10000 points grid.
mcResVolLocale7 = backtest.MonteCarloPricerVectorized(S0,
dataSetTest,
bootstrap,
nbPaths,
nbTimeStep,
nnVolLocale7)
mcResVolLocale7.head()
#Diagnose backtest results
plotTools.predictionDiagnosis(mcResVolLocale7["Price"],
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax = KMax)
mcResVolLocale7.to_csv("mcResVolLocale7.csv")
importlib.reload(backtest)
#PDE backtest with a cranck nicholson scheme
pdeResVolLocale7 = backtest.PDEPricerVectorized(dataSetTest, S0, nnVolLocale7, bootstrap)
pdeResVolLocale7.head()
#Backtest diagnosis
plotTools.predictionDiagnosis(pdeResVolLocale7,
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
pdeResVolLocale7.to_csv(workingFolder + "pdeResVolLocale7.csv")
#Local volatility is returned through linear interpolation on neural local volatilities obtained on the testing grid.
mcResVolLocale8 = backtest.MonteCarloPricerVectorized(S0,
dataSetTest,
bootstrap,
nbPaths,
nbTimeStep,
nnVolLocale8)
mcResVolLocale8.head()
plotTools.predictionDiagnosis(mcResVolLocale8["Price"],
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax = KMax)
mcResVolLocale8.to_csv("mcResVolLocale8.csv")
pdeResVolLocale8 = backtest.PDEPricerVectorized(dataSetTest, S0, nnVolLocale8, bootstrap)
pdeResVolLocale8.head()
plotTools.predictionDiagnosis(pdeResVolLocale8,
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
pdeResVolLocale8.to_csv(workingFolder + "pdeResVolLocale8.csv")
Yon can skip the training step by loading in the left panel in colab workspace tensorflow models. These models are contained in the results folder of github repository.
hyperparameters = {}
#penalization coefficient
hyperparameters["lambdaLocVol"] = 10#1#0.001#0.01 #100
hyperparameters["lambdaSoft"] = 1000#100#0.0001#10#10 #100
hyperparameters["lambdaGamma"] = 10000000#10000#100#10 #10000
#Derivative soft constraints parameters
hyperparameters["lowerBoundTheta"] = 0.01
hyperparameters["lowerBoundGamma"] = 0.00001
#Local variance parameters
hyperparameters["DupireVarCap"] = 10
hyperparameters["DupireVolLowerBound"] = 0.05
hyperparameters["DupireVolUpperBound"] = 0.40
#Learning scheduler coefficient
hyperparameters["LearningRateStart"] = 0.1
hyperparameters["Patience"] = 100
hyperparameters["batchSize"] = 50
hyperparameters["FinalLearningRate"] = 1e-6
hyperparameters["FixedLearningRate"] = False
#Training parameters
hyperparameters["nbUnits"] = 100 #number of units for hidden layers
hyperparameters["maxEpoch"] = 10000#10000 #maximum number of epochs
importlib.reload(plotTools)
#Execute this cell if you want to fit neural network with prices
res = neuralNetwork.create_train_model(neuralNetwork.NNArchitectureVanillaSoftDupire,
scaledDataSet,
True,
hyperparameters,
scaler,
modelName = "convexSoftVolModel")
y_pred4, volLocale4, dNN_T4, gNN_K4, lossSerie4 = res
plotTools.plotEpochLoss(lossSerie4)
lossSerie4.iloc[-1]
importlib.reload(plotTools)
# Evaluate results on the training set, you can execute that cell without training the model
res = neuralNetwork.create_eval_model(neuralNetwork.NNArchitectureVanillaSoftDupire,
scaledDataSet,
True,
hyperparameters,
scaler,
modelName = "convexSoftVolModel")
y_pred4, volLocale4, dNN_T4, gNN_K4, lossSerie4 = res
plotTools.modelSummary(y_pred4,
volLocale4,
dNN_T4,
gNN_K4,
dataSet,
S0,
bootstrap,
yMin = KMin,
yMax = KMax,
thresholdPrice = None,
removeNaN = False,
savePath = "NeuralPriceTrain")
volLocale4.loc[(midS0,slice(None))]
# Evaluate results on the testing dataset, you can execute that cell without training the model
res = neuralNetwork.create_eval_model(neuralNetwork.NNArchitectureVanillaSoftDupire,
scaledDataSetTest,
True,
hyperparameters,
scaler,
modelName = "convexSoftVolModel")
y_pred4Test, volLocale4Test, dNN_T4Test, gNN_K4Test, lossSerie4TestG = res
plotTools.modelSummary(y_pred4Test,
volLocale4Test,
dNN_T4Test,
gNN_K4Test,
dataSetTest,
S0,
bootstrap,
yMin = KMin,
yMax = KMax,
thresholdPrice = None,
removeNaN = False,
savePath = "NeuralPriceTest")
During Monte Carlo backtest, each option in testing set is priced with an underlying which is diffused with the following SDE : $$ dS_t = \left( r_t - q_t - \frac{\sigma_{NN}^2(t, S_t)}{2} \right) dt + \sigma_{NN}(t, S_t) dW_t$$ with $\sigma_{NN}$ the neural local volatility function.
Due to computation time issue we avoid to make millions of call to neural network and we interpolate linearly neural local volatility obtained on one the two possible grid :
During PDE backtest, we used a crank-nicholson scheme to revaluate each option in our testing set. Time step corresponds to one day and space grid has 100 points.
S0
# Function which evaluates neural local volatility when neural network is fitted on prices
def neuralVolLocalePrix(s,t):
vLoc = neuralNetwork.evalVolLocale(neuralNetwork.NNArchitectureVanillaSoftDupire,
s, t,
dataSet,
hyperparameters,
scaler,
bootstrap,
S0,
modelName = "convexSoftVolModel")
return vLoc.dropna()
importlib.reload(neuralNetwork)
#Artificial grid with 10000 points which aims to limit the impact of linear interpolation
strikeLow = min(dataSet["Strike"].min(),dataSetTest["Strike"].min()) #dataSet["Strike"].min()
strikeUp = max(dataSet["Strike"].max(),dataSetTest["Strike"].max()) #dataSet["Strike"].max()
strikeGrid = np.linspace(strikeLow, strikeUp, 100)
matLow = min(dataSet["Maturity"].min(),dataSetTest["Maturity"].min()) #dataSet["Maturity"].min()
matUp = max(dataSet["Maturity"].max(),dataSetTest["Maturity"].max()) #dataSetTest["Maturity"].max()
matGrid = np.linspace(matLow, matUp, 100)
volLocaleGrid = np.meshgrid(strikeGrid, matGrid)
volLocalInterp = neuralVolLocalePrix(volLocaleGrid[0].flatten(),
volLocaleGrid[1].flatten())
volLocalInterp.head()
volLocalInterp2 = neuralVolLocalePrix(dataSetTest.index.get_level_values("Strike").values.flatten(),
dataSetTest.index.get_level_values("Maturity").values.flatten())
volLocalInterp2.head()
#Local volatility function for backtests
nnVolLocale = lambda x,y : backtest.interpolatedMCLocalVolatility(volLocalInterp, x, y)
#Local volatility function for backtests
nnVolLocale2 = lambda x,y : backtest.interpolatedMCLocalVolatility(volLocalInterp2, x, y)
plotTools.plotSerie(volLocalInterp,
Title = 'Interpolated Local Volatility Surface',
az=30,
yMin=KMin,
yMax=KMax,
zAsPercent=True)
plotTools.plotSerie(volLocalInterp2,
Title = 'Interpolated Local Volatility Surface',
az=30,
yMin=KMin,
yMax=KMax,
zAsPercent=True)
Monte Carlo backtest, each option in testing set is priced with an underlying which is diffused with the following SDE : $$ dS_t = \left( r_t - q_t - \frac{\sigma_{NN}^2(t, S_t)}{2} \right) dt + \sigma_{NN}(t, S_t) dW_t$$ with $\sigma_{NN}$ the neural local volatility function.
nbTimeStep = 100
nbPaths = 100000
#Local volatility is returned through linear interpolation on neural local volatilities obtained on the 10000 points grid.
mcResVolLocalePrix = backtest.MonteCarloPricerVectorized(S0,
dataSetTest,
bootstrap,
nbPaths,
nbTimeStep,
nnVolLocale)
mcResVolLocalePrix.head()
plotTools.predictionDiagnosis(mcResVolLocalePrix["Price"],
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax = KMax)
mcResVolLocalePrix.to_csv("mcResVolLocalePrix.csv")
#PDE backtest with cranck-nicolson scheme
pdeResVolLocale = backtest.PDEPricerVectorized(dataSetTest, S0, nnVolLocale, bootstrap)
pdeResVolLocale.head()
plotTools.predictionDiagnosis(pdeResVolLocale,
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
pdeResVolLocale.to_csv(workingFolder + "pdeResVolLocale.csv")
#Local volatility is returned through linear interpolation on neural local volatilities obtained on the testing grid.
mcResVolLocalePrix2 = backtest.MonteCarloPricerVectorized(S0,
dataSetTest,
bootstrap,
nbPaths,
nbTimeStep,
nnVolLocale2)
mcResVolLocalePrix2.head()
plotTools.predictionDiagnosis(mcResVolLocalePrix2["Price"],
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax = KMax)
mcResVolLocalePrix2.to_csv("mcResVolLocalePrix2.csv")
#PDE backtest with cranck-nicolson scheme
pdeResVolLocale2 = backtest.PDEPricerVectorized(dataSetTest, S0, nnVolLocale2, bootstrap)
pdeResVolLocale2.head()
plotTools.predictionDiagnosis(pdeResVolLocale2,
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
pdeResVolLocale2.to_csv(workingFolder + "pdeResVolLocale2.csv")
#Random selection of several hyperparameters
neuralNetwork.selectHyperparametersRandom(hyperparameters,
["lambdaLocVol","lambdaSoft","lambdaGamma"],
neuralNetwork.NNArchitectureVanillaSoftGatheral,
"hyperParameters",
True,
100,
scaledDataSet,
scaler,
trainedOnPrice = False,
logGrid = True)
#hyperparameters["lambdaLocVol"] = 100
#hyperparameters["lambdaSoft"] = 100
#hyperparameters["lambdaGamma"] = 10000
hyperparameters["lambdaLocVol"] = 0.01#0.01 #100
hyperparameters["lambdaSoft"] = 0.01#10#10 #100
hyperparameters["lambdaGamma"] = 100#10 #10000
#marginal selection of hypterparameters
neuralNetwork.selectHyperparameters(hyperparameters,
"lambdaLocVol",
neuralNetwork.NNArchitectureVanillaSoftGatheral,
"hyperParameters",
True,
scaledDataSet,
scaler,
trainedOnPrice = False,
logGrid = True)
neuralNetwork.selectHyperparameters(hyperparameters,
"DupireVarCap",
neuralNetwork.NNArchitectureVanillaSoftGatheral,
"hyperParameters",
True,
scaledDataSet,
scaler,
trainedOnPrice = False,
logGrid = True)
neuralNetwork.selectHyperparameters(hyperparameters,
"lambdaLocVol",
neuralNetwork.NNArchitectureVanillaSoftGatheral,
"hyperParameters",
True,
scaledDataSet,
scaler,
trainedOnPrice = False,
logGrid = True)
hyperparameters["lambdaLocVol"] = 100
neuralNetwork.selectHyperparameters(hyperparameters,
"lambdaLocVol",
neuralNetwork.NNArchitectureVanillaSoftGatheral,
"hyperParameters",
True,
scaledDataSet,
scaler,
trainedOnPrice = False,
logGrid = True)
hyperparameters["nbUnits"] = 40
neuralNetwork.selectHyperparameters(hyperparameters,
"nbUnits",
neuralNetwork.NNArchitectureVanillaSoftGatheral,
"hyperParameters",
True,
scaledDataSet,
scaler,
trainedOnPrice = False,
logGrid = False)
hyperparameters["nbUnits"] = 200
neuralNetwork.selectHyperparameters(hyperparameters,
"lambdaLocVol",
neuralNetwork.NNArchitectureVanillaSoftGatheral,
"hyperParameters",
True,
scaledDataSet,
scaler,
trainedOnPrice = False,
logGrid = True)
hyperparameters["nbUnits"] = 40
neuralNetwork.selectHyperparameters(hyperparameters,
"nbUnits",
neuralNetwork.NNArchitectureVanillaSoftGatheral,
"hyperParameters",
True,
scaledDataSet,
scaler,
trainedOnPrice = False,
logGrid = False)
hyperparameters["nbUnits"] = 200
importlib.reload(loadData)
Implementation is inspired from Matlab code of Philipp Rindler :
np.seterr(all='warn')
importlib.reload(BS)
#parameters, theta, maturities, pSSVI = SSVI.train_svi_surface(dataSet, S0)
SSVIModel = SSVI.SSVIModel(S0, bootstrap)
SSVIModel.fit(dataSet)
serie = SSVIModel.eval(dataSetTest)
serieTrain = SSVIModel.eval(dataSet)
keptPrices = dataSet["Price"][dataSet["Price"] >= 0.0].index
plotTools.predictionDiagnosis(plotTools.selectIndex(serieTrain, keptPrices) ,
plotTools.selectIndex(dataSet["ImpliedVol"], keptPrices) ,
" Implied vol ",
yMin=KMin,
yMax=KMax)
impPriceTraining = plotTools.plotImpliedVolPrices(np.square(serieTrain)*serieTrain.index.get_level_values("Maturity"),
bootstrap,
S0,
dataSet,
yMin = KMin,
yMax = KMax,
thresholdPrice = None)
ImpVolPutTrainingSSVI = BS.vectorizedImpliedVolatilityCalibration(S0, bootstrap,
dataSet["Maturity"],
dataSet["Strike"],
dataSet["OptionType"],
impPriceTraining,
removeNaN = False)
ImpVolPutTrainingSSVI = pd.Series(ImpVolPutTrainingSSVI, index = dataSet.index).sort_index()
keptPrices = dataSet["Price"][dataSet["Price"] >= 0.0].index
plotTools.predictionDiagnosis(plotTools.selectIndex(ImpVolPutTrainingSSVI, keptPrices) ,
plotTools.selectIndex(dataSet["ImpliedVol"], keptPrices) ,
" Implied vol ",
yMin=KMin,
yMax=KMax)
keptPrices = dataSet["Price"][dataSet["Price"] >= 0.0].index
plotTools.predictionDiagnosis(plotTools.selectIndex(ImpVolPutTrainingSSVI, keptPrices),
plotTools.selectIndex(serieTrain, keptPrices),
" Implied vol ",
yMin=KMin,
yMax=KMax)
plotTools.predictionDiagnosis(SSVI.impliedVariance(serie),
SSVI.impliedVariance(dataSetTest["ImpliedVol"]),
" Implied total variance ",
yMin=KMin,
yMax=KMax)
keptPrices = dataSetTest["Price"][dataSetTest["Price"] >= 0.0].index
plotTools.predictionDiagnosis(plotTools.selectIndex(serie, keptPrices),
plotTools.selectIndex(dataSetTest["ImpliedVol"], keptPrices),
" Implied vol ",
yMin=KMin,
yMax=KMax)
impPriceTesting = plotTools.plotImpliedVolPrices(np.square(serie)*serie.index.get_level_values("Maturity"),
bootstrap,
S0,
dataSetTest,
yMin = KMin,
yMax = KMax,
thresholdPrice = threshold)
ImpVolPutTestingSSVI = BS.vectorizedImpliedVolatilityCalibration(S0, bootstrap,
dataSetTest["Maturity"],
dataSetTest["Strike"],
dataSetTest["OptionType"],
impPriceTesting,
removeNaN = False)
ImpVolPutTestingSSVI = pd.Series(ImpVolPutTestingSSVI, index = dataSetTest.index).sort_index()
keptPrices = dataSetTest["Price"][dataSetTest["Price"] >= 0.0].index
plotTools.predictionDiagnosis(plotTools.selectIndex(ImpVolPutTestingSSVI, keptPrices),
plotTools.selectIndex(dataSetTest["ImpliedVol"], keptPrices),
" Implied vol ",
yMin=KMin,
yMax=KMax)
#dT, hk, dK, locVolSSVI, density = finiteDifferenceSVI(dataSet, interpolateWithSSVI)
dTTrain, hkTrain, dKTrain, locVolSSVITrain, densityTrain = SSVI.finiteDifferenceSVI(dataSet, SSVIModel.eval)
plotTools.diagnoseLocalVol(dTTrain,
locVolSSVITrain,
densityTrain,
SSVIModel.eval(dataSet),
dataSet,
az=320,
yMin=KMin,
yMax=KMax)
#dT, hk, dK, locVolSSVI, density = finiteDifferenceSVI(dataSet, interpolateWithSSVI)
dT, hk, dK, locVolSSVI, density = SSVI.finiteDifferenceSVI(dataSetTest, SSVIModel.eval)
plotTools.diagnoseLocalVol(dT,
locVolSSVI,
density,
SSVIModel.eval(dataSetTest),
dataSetTest,
az=320,
yMin=KMin,
yMax=KMax)
plotTools.saveDataModel(plotTools.removeDuplicateIndex(impPriceTraining),
plotTools.removeDuplicateIndex(locVolSSVITrain),
plotTools.removeDuplicateIndex(serieTrain) ,
"./Results/SSVITraining")
plotTools.saveDataModel(plotTools.removeDuplicateIndex(impPriceTesting),
plotTools.removeDuplicateIndex(locVolSSVI),
plotTools.removeDuplicateIndex(serie) ,
"./Results/SSVITesting")
strikeLow = min(dataSet["Strike"].min(),dataSetTest["Strike"].min()) #dataSet["Strike"].min()
strikeUp = max(dataSet["Strike"].max(),dataSetTest["Strike"].max()) #dataSet["Strike"].max()
strikeGrid = np.linspace(strikeLow, strikeUp, 100)
matLow = min(dataSet["Maturity"].min(),dataSetTest["Maturity"].min()) #dataSet["Maturity"].min()
matUp = max(dataSet["Maturity"].max(),dataSetTest["Maturity"].max()) #dataSetTest["Maturity"].max()
matGrid = np.linspace(matLow, matUp, 100)
volLocaleGrid = np.meshgrid(strikeGrid, matGrid)
volLocaleGridDf = pd.DataFrame(np.vstack((np.ravel(volLocaleGrid[0]), np.ravel(volLocaleGrid[1]))).T,
columns = ["Strike", "Maturity"]).set_index(["Strike","Maturity"],drop=False)
volLocaleGridDf["ChangedStrike"] = bootstrap.changeOfVariable(volLocaleGridDf["Strike"], volLocaleGridDf["Maturity"])[0]
volLocaleGridDf["logMoneyness"] = np.log(volLocaleGridDf["ChangedStrike"] / S0)
volLocaleGridDf["OptionType"] = np.ones_like(volLocaleGridDf["logMoneyness"])
dT, hk, dK, locVolSSVI, density = SSVI.finiteDifferenceSVI(volLocaleGridDf, SSVIModel.eval)
plotTools.plotSerie(locVolSSVI.dropna(),
yMin=KMin,
yMax=KMax,
az=105,
Title = 'Local volatility')
plotTools.plotSerie(locVolSSVI.dropna()[locVolSSVI.dropna() <= 1.0],
yMin=KMin,
yMax=KMax,
az=105,
Title = 'Local volatility')
During Monte Carlo backtest, each option in testing set is priced with an underlying which is diffused with the following SDE : $$ dS_t = \left( r_t - q_t - \frac{\sigma_{NN}^2(t, S_t)}{2} \right) dt + \sigma_{NN}(t, S_t) dW_t$$ with $\sigma_{NN}$ the neural local volatility function.
Due to computation time issue we avoid to make millions of call to neural network and we interpolate linearly neural local volatility obtained on one the two possible grid :
During PDE backtest, we used a crank-nicholson scheme to revaluate each option in our testing set. Time step corresponds to one day and space grid has 100 points.
nbTimeStep = 100
nbPaths = 100
#dT, hk, dK, locVolSSVI, density = finiteDifferenceSVI(dataSet, interpolateWithSSVI)
dT, hk, dK, locVolSSVI, density = SSVI.finiteDifferenceSVI(dataSetTest, SSVIModel.eval)
nnSSVI = lambda x,y : backtest.interpolatedMCLocalVolatility(locVolSSVI, x, y)
strikeLow = min(dataSet["Strike"].min(),dataSetTest["Strike"].min()) #dataSet["Strike"].min()
strikeUp = max(dataSet["Strike"].max(),dataSetTest["Strike"].max()) #dataSet["Strike"].max()
strikeGrid = np.linspace(strikeLow, strikeUp, 100)
matLow = min(dataSet["Maturity"].min(),dataSetTest["Maturity"].min()) #dataSet["Maturity"].min()
matUp = max(dataSet["Maturity"].max(),dataSetTest["Maturity"].max()) #dataSetTest["Maturity"].max()
matGrid = np.linspace(matLow, matUp, 100)
volLocaleGrid = np.meshgrid(strikeGrid, matGrid)
volLocaleGridDf = pd.DataFrame(np.vstack((np.ravel(volLocaleGrid[0]), np.ravel(volLocaleGrid[1]))).T,
columns = ["Strike", "Maturity"]).set_index(["Strike","Maturity"],drop=False)
volLocaleGridDf["ChangedStrike"] = bootstrap.changeOfVariable(volLocaleGridDf["Strike"], volLocaleGridDf["Maturity"])[0]
volLocaleGridDf["logMoneyness"] = np.log(volLocaleGridDf["ChangedStrike"] / S0)
volLocaleGridDf["OptionType"] = np.ones_like(volLocaleGridDf["logMoneyness"])
volLocaleGridDf.head()
#dT, hk, dK, locVolSSVI, density = finiteDifferenceSVI(dataSet, interpolateWithSSVI)
dT2, hk2, dK2, locVolSSVI2, density2 = SSVI.finiteDifferenceSVI(volLocaleGridDf, SSVIModel.eval)
cleanValue = ~(dT2.isna() | density2.isna() | (dT2 < 0) | (density2 < 0) )
dT2 = dT2[cleanValue.values]
density2 = density2[cleanValue.values]
hk2 = hk2[cleanValue.values]
locVolSSVI2 = locVolSSVI2[cleanValue.values]
dK2 = dK2[cleanValue.values]
nnSSVI2 = lambda x,y : backtest.interpolatedMCLocalVolatility(locVolSSVI2, x, y)
mcResSSVITest = backtest.MonteCarloPricerVectorized(S0,
dataSetTest,
bootstrap,
nbPaths,
nbTimeStep,
nnSSVI)
mcResSSVITest.head()
mcResSSVITest.to_csv(workingFolder + "mcResSSVITest.csv")
plotTools.predictionDiagnosis(mcResSSVITest["Price"],
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
mcResSSVITest2 = backtest.MonteCarloPricerVectorized(S0,
dataSetTest,
bootstrap,
nbPaths,
nbTimeStep,
nnSSVI2)
mcResSSVITest2.head()
plotTools.predictionDiagnosis(mcResSSVITest2["Price"],
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
mcResSSVITest2.to_csv(workingFolder + "mcResSSVITest2.csv")
plotTools.plotSerie(testingDataSetSSVI[testingDataSetSSVI.index.get_level_values("Maturity") > 0.01], Title = 'SSVI Local Volatility Surface', az=30, yMin=0.0S0, yMax=2.0S0, zAsPercent=True)
pdeResSigmaSSVITest = backtest.PDEPricerVectorized(dataSetTest, S0, nnSSVI, bootstrap)
pdeResSigmaSSVITest.head()
plotTools.predictionDiagnosis(pdeResSigmaSSVITest,
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
pdeResSigmaSSVITest.to_csv(workingFolder + "pdeResSigmaSSVITest.csv")
pdeResSigmaSSVITest2 = backtest.PDEPricerVectorized(dataSetTest, S0, nnSSVI2, bootstrap)
pdeResSigmaSSVITest2.head()
plotTools.predictionDiagnosis(pdeResSigmaSSVITest2,
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
pdeResSigmaSSVITest2.to_csv(workingFolder + "pdeResSigmaSSVITest2.csv")
During Monte Carlo backtest, each option in testing set is priced with an underlying which is diffused with the following SDE : $$ dS_t = \left( r_t - q_t - \frac{\sigma_{NN}^2(t, S_t)}{2} \right) dt + \sigma_{NN}(t, S_t) dW_t$$ with $\sigma_{NN}$ the neural local volatility function.
Due to computation time issue we avoid to make millions of call to neural network and we interpolate linearly neural local volatility obtained on one the two possible grid :
For the tikhonov case we only use one grid which contains the node of the trinomial tree used for the calibration algorithm. See CREPEY, Stéphane. Calibration of the local volatility in a trinomial tree using Tikhonov regularization. Inverse Problems, 2002, vol. 19, no 1, p. 91.
During PDE backtest, we used a crank-nicholson scheme to revaluate each option in our testing set. Time step corresponds to one day and space grid has 100 points.
nbTimeStep = 100
nbPaths = 100000
nnVolLocaleTykhonov = lambda x,y : backtest.interpolatedMCLocalVolatility(localVolatility["LocalVolatility"], x, y)
s = testingDataSetAresky.index.get_level_values("Strike").values
t = testingDataSetAresky.index.get_level_values("Maturity").values
tikhonovLocVol = nnVolLocaleTykhonov(s, t)
plotTools.plotSerie(tikhonovLocVol[tikhonovLocVol.index.get_level_values("Maturity") > 0.01],
Title = 'Tikhonov Local Volatility Surface',
az=30,
yMin=0.0*S0,
yMax=2.0*S0,
zAsPercent=True)
mcResTikhonovTest = backtest.MonteCarloPricerVectorized(S0,
dataSetTest,
bootstrap,
nbPaths,
nbTimeStep,
nnVolLocaleTykhonov)
mcResTikhonovTest.head()
plotTools.predictionDiagnosis(mcResTikhonovTest,
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
mcResTikhonovTest.to_csv(workingFolder + "mcResTikhonovTest.csv")
mcResTikhonovTrain = backtest.MonteCarloPricerVectorized(S0,
dataSet,
bootstrap,
nbPaths,
nbTimeStep,
nnVolLocaleTykhonov)
mcResTikhonovTrain.head()
plotTools.predictionDiagnosis(mcResTikhonovTrain,
dataSet["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
mcResTikhonovTrain.to_csv(workingFolder + "mcResTikhonovTrain.csv")
pdeResSigmaTikhonovTrain = backtest.PDEPricerVectorized(dataSet, S0, nnVolLocaleTykhonov, bootstrap)
pdeResSigmaTikhonovTrain.head()
plotTools.predictionDiagnosis(pdeResSigmaTikhonovTrain,
dataSet["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
pdeResSigmaTikhonovTrain.to_csv(workingFolder + "pdeResSigmaTikhonovTrain.csv")
pdeResSigmaTikhonovTest = backtest.PDEPricerVectorized(dataSetTest, S0, nnVolLocaleTykhonov, bootstrap)
pdeResSigmaTikhonovTest.head()
plotTools.predictionDiagnosis(pdeResSigmaTikhonovTest,
dataSetTest["Price"],
" Price ",
yMin=KMin,
yMax=KMax)
pdeResSigmaTikhonovTest.to_csv(workingFolder + "pdeResSigmaTikhonovTest.csv")
importlib.reload(backtest)
mcResGPTest = backtest.loadMCPrices("./Results/mcResGPTest.csv",
parseHeader=0)
mcResGPTest.head()
mcResSSVITest = backtest.loadMCPrices("./Results/mcResSSVITest.csv",
parseHeader=0)
mcResSSVITest.head()
backtest.rmse(mcResGPTest["Price"], mcResSSVITest["Price"])
pdeResSigmaGPTest = backtest.loadMCPrices("./Results/pdeResSigmaGPTest.csv")
pdeResSigmaGPTest.head()
This section removes nodes where implied volatility estimation is impossible for some models (GP, NN Price). This allow a fair comparison of implied volatility for model.
#Detecting options which lead to implied volatilities miscalibration
gpImpVolTraining = pd.Series(pd.read_csv("./Results" + "/GPTraining.csv").set_index(["Strike","Maturity"])["ImpliedVol"],
index = plotTools.removeDuplicateIndex(dataSet).index)
gpImpVolTesting = pd.Series(pd.read_csv("./Results" + "/GPTesting.csv").set_index(["Strike","Maturity"])["ImpliedVol"],
index = plotTools.removeDuplicateIndex(dataSetTest).index)
nnImpVolTraining = pd.Series(pd.read_csv("./Results" + "/NeuralPriceTrain.csv").set_index(["Strike","Maturity"])["ImpliedVol"],
index = plotTools.removeDuplicateIndex(dataSet).index)
nnImpVolTesting = pd.Series(pd.read_csv("./Results" + "/NeuralPriceTest.csv").set_index(["Strike","Maturity"])["ImpliedVol"],
index = plotTools.removeDuplicateIndex(dataSetTest).index)
gpImpVolTrainingError = gpImpVolTraining[gpImpVolTraining <= 1e-9].append(gpImpVolTraining[gpImpVolTraining >= 2])
nnImpVolTrainingError = nnImpVolTraining[nnImpVolTraining <= 1e-9].append(nnImpVolTraining[nnImpVolTraining >= 2])
trainingFilterImpVol = gpImpVolTrainingError.append(nnImpVolTrainingError).index.unique()
gpImpVolTestingError = gpImpVolTesting[gpImpVolTesting <= 1e-9].append(gpImpVolTesting[gpImpVolTesting >= 2])
nnImpVolTestingError = nnImpVolTesting[nnImpVolTesting <= 1e-9].append(nnImpVolTesting[nnImpVolTesting >= 2])
testingFilterImpVol = gpImpVolTestingError.append(nnImpVolTestingError).index.unique()
#Load results from another model
dfModelTraining = pd.read_csv("./Results" + "/NeuralImpliedVolTrain.csv").set_index(["Strike","Maturity"])
dfModelTraining = pd.DataFrame(dfModelTraining.values,
index = plotTools.removeDuplicateIndex(dataSet).index,
columns = dfModelTraining.columns)
keptVols = dataSet["ImpliedVol"].drop(trainingFilterImpVol).index
plotTools.predictionDiagnosis(plotTools.selectIndex(dfModelTraining["ImpliedVol"], keptVols) ,
plotTools.selectIndex(dataSet["ImpliedVol"], keptVols) ,
" Implied vol ",
yMin=KMin,
yMax=KMax)
dfModelTesting = pd.read_csv("./Results" + "/NeuralImpliedVolTest.csv").set_index(["Strike","Maturity"])
dfModelTesting = pd.DataFrame(dfModelTesting.values,
index = plotTools.removeDuplicateIndex(dataSetTest).index,
columns = dfModelTesting.columns)
keptVols = dataSetTest["ImpliedVol"].drop(testingFilterImpVol).index
plotTools.predictionDiagnosis(plotTools.selectIndex(dfModelTesting["ImpliedVol"], keptVols) ,
plotTools.selectIndex(dataSetTest["ImpliedVol"], keptVols) ,
" Implied vol ",
yMin=KMin,
yMax=KMax)